iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
Software Development

30 天打造專屬盯盤機器人:資料爬取、策略指標到 Telegram/LINE 推播系列 第 21 篇

Day 21:UI/UX 大升級 — 導入 Telegram 互動式按鈕選單!

  • 分享至 

  • xImage
  •  

今天是第二十一天!昨天(第二十天)我們成功幫機器人補齊了最後一塊拼圖——「消息面新聞與情緒分析」,讓我們的 Telegram 機器人擁有了「技術 + 籌碼 + 基本 + 消息」四維度全方位看盤能力!

不過隨著功能越來越多,如果你只是想看個「籌碼數據」或「最新新聞」,卻每次都要接收一長串幾百字的圖文大報告,手機畫面很快就會被塞滿。更麻煩的是,如果每次都要手動打指令(如 /stock 2330),在手機小螢幕上打字總是少了一點順暢感!

今天我們的目標,就是要幫 Telegram 機器人進行 UI/UX 介面大升級!導入 Inline Keyboard 與CallbackQueryHandler,讓你只需動動手指,點擊「技術線圖」、「籌碼動向」、「即時新聞」或「營收數據」,機器人就會立刻為你切換對應的精緻卡片!
步驟一:先釐清邏輯
Telegram 的互動式按鈕運作邏輯非常優雅:

  • 建立按鈕選單(InlineKeyboardMarkup):在發送 Telegram 訊息時,附帶一組按鈕矩陣,每個按鈕都有對應的 callback_data(例如 cb_kline_2330.TW)。
  • 監聽按鈕點擊(CallbackQueryHandler):當使用者在 Telegram 聊天視窗中按下按鈕時,Telegram 會向我們的伺服器發送一個 CallbackQuery 事件。
  • 動態更新內容(Edit Message):機器人收到點擊事件後,呼叫 query.edit_message_text() 或 edit_message_media(),直接在原本的訊息上「動態更新內容與圖片」,不用一直刷新訊息!

接下來,知道邏輯後,我們就可以來更改程式碼了!
步驟二:完整實作程式碼
我們主要會更改bot.py,並在 main.py 註冊按鈕事件處理器!

  1. bot.py(新增按鈕選單與點擊事件處理)
import os
import time
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ContextTypes, CommandHandler, CallbackQueryHandler
from fetcher import get_stock_data
from chart import create_stock_chart
from config import CHAT_ID, DEFAULT_STOCKS

# 建立動態按鈕選單
def get_stock_keyboard(symbol):
    keyboard = [
        [
            InlineKeyboardButton("技術線圖", callback_data=f"chart_{symbol}"),
            InlineKeyboardButton("籌碼動向", callback_data=f"chip_{symbol}")
        ],
        [
            InlineKeyboardButton("營收數據", callback_data=f"rev_{symbol}"),
            InlineKeyboardButton("即時新聞", callback_data=f"news_{symbol}")
        ]
    ]
    return InlineKeyboardMarkup(keyboard)

# /stock 指令主處理邏輯
async def stock_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if not context.args:
        await update.message.reply_text("請輸入股票代碼,例如:/stock 2330")
        return

    raw_symbol = context.args[0]
    await update.message.reply_text(f"[INFO] 正在獲取 {raw_symbol} 完整資料,請稍後...")

    symbol, df, chip_data, revenue_data, news_data = get_stock_data(raw_symbol)

    if df is None:
        await update.message.reply_text(f"[ERROR] 找不到股票代碼:{raw_symbol}")
        return

    latest = df.iloc[-1]
    summary_text = (
        f"【{symbol} 個股儀表板】\n"
        f"日期:{df.index[-1].strftime('%Y-%m-%d')}\n"
        f"最新收盤價:{latest['Close']:.2f} 元\n\n"
        f"請選擇下方按鈕切換詳細看盤維度:"
    )

    # 發送帶有互動按鈕的主選單
    await update.message.reply_text(
        text=summary_text,
        reply_markup=get_stock_keyboard(symbol)
    )

# 按鈕點擊事件處理器 (CallbackQueryHandler)
async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()  # 告知 Telegram 已收到按鈕點擊,消除按鈕加載狀態

    data = query.data
    action, symbol = data.split("_", 1)

    # 重新獲取資料(會優先使用 SQLite 快取)
    _, df, chip_data, revenue_data, news_data = get_stock_data(symbol)

    if action == "chart":
        img_path = create_stock_chart(symbol, df)
        with open(img_path, 'rb') as photo:
            await query.message.reply_photo(
                photo=photo,
                caption=f"【{symbol} K線與技術指標線圖】",
                reply_markup=get_stock_keyboard(symbol)
            )
        if os.path.exists(img_path):
            os.remove(img_path)

    elif action == "chip":
        text = (
            f"【{symbol} 三大法人籌碼動向】(單位: 張)\n"
            f"-----------------------------------\n"
            f"外資:{chip_data['foreign']:+d}\n"
            f"投信:{chip_data['investment']:+d}\n"
            f"自營商:{chip_data['dealer']:+d}\n"
            f"-----------------------------------\n"
            f"法人合計:{chip_data['total']:+d}\n"
        )
        await query.message.reply_text(text=text, reply_markup=get_stock_keyboard(symbol))

    elif action == "rev":
        text = (
            f"【{symbol} 最新月營收報告】({revenue_data['month']})\n"
            f"-----------------------------------\n"
            f"單月營收:{revenue_data['revenue'] / 100000:.2f} 億元\n"
            f"MoM (月增率):{revenue_data['mom']:+.1f}%\n"
            f"YoY (年增率):{revenue_data['yoy']:+.1f}%\n"
        )
        await query.message.reply_text(text=text, reply_markup=get_stock_keyboard(symbol))

    elif action == "news":
        text = f"【{symbol} 即時消息面分析】({news_data['sentiment']})\n-----------------------------------\n"
        for idx, item in enumerate(news_data["list"], 1):
            text += f"{idx}. {item['title']}\n"
        await query.message.reply_text(text=text, reply_markup=get_stock_keyboard(symbol))

def setup_handlers(app):
    app.add_handler(CommandHandler("start", lambda u, c: u.message.reply_text("歡迎使用台股看盤小幫手!")))
    app.add_handler(CommandHandler("stock", stock_command))
    # 註冊按鈕點擊處理器
    app.add_handler(CallbackQueryHandler(button_callback))
  1. main.py (不需要更改)
    main.py 保持模組化呼叫 setup_handlers(app) 即可,按鈕事件處理器會被自動註冊進去!

解釋一下程式碼吧!
InlineKeyboardButton 與 callback_data:
我們為每個按鈕綁定了如 chart_2330.TW 的資料,這樣點擊按鈕時,後台就能精準知道「使用者按了哪一個功能的按鈕」以及「指的是哪一檔股票」。

query.answer():
這是 Telegram Bot API 非常關鍵的小細節!在處理 Callback 時呼叫 await query.answer(),可以讓 Telegram 用戶端手機上的按鈕轉圈圈動畫立刻消失,給使用者最順暢的點擊體驗。
步驟三:執行與測試
在終端機輸入:

python main.py

小結一下:
透過按鈕選單,我們大幅提升了 Telegram Bot 的操作質感,將原本單向的訊息推播,升級成直覺又現代化的互動式 App 體驗!
今天我們成功導入了 Telegram Inline Keyboard 與 CallbackQueryHandler!


上一篇
Day 20:消息面大升級 — 導入即時個股新聞爬蟲與利多利空情緒分析!
下一篇
Day 22:自選股管理系統 — 導入 Telegram 互動指令與資料庫動態追蹤!
系列文
30 天打造專屬盯盤機器人:資料爬取、策略指標到 Telegram/LINE 推播 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言